﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;

public class GenoType 
{
    public Dictionary<char, string> ProductionRules = new Dictionary<char, string>();
    public string Axiom = "";
    public string System = "";
    public int Depth = 3;

    public float Fitness = 0.0f;

    public GenoType(int depth)
    {
        LSystem lsystem = new LSystem(depth, ref ProductionRules, ref Axiom, ref System);
    }

    public GenoType(GenoType genoType)
    {
        this.ProductionRules = new Dictionary<char, string>(genoType.ProductionRules);
        this.Axiom = genoType.Axiom;
        this.Depth = genoType.Depth;

        LSystem lsystem = new LSystem();
        this.System = lsystem.CreateFromGenoType(this.ProductionRules, this.Axiom, Depth);
    }

    public void Prepare()
    {
        LSystem lsystem = new LSystem();
        this.System = lsystem.CreateFromGenoType(this.ProductionRules, this.Axiom, Depth);
    }

    private void MutateAxiom(float rand)
    {
        float axeAddLiteral = 0.03f;
        if (rand < axeAddLiteral)
        {
            // Mutation is to add a literal to the axiom - which is a joint and a body
            Axiom += LSystem.AddJointRule() + LSystem.AddBodyRule();
        }
        else
        {
            // Mutation is to mutate a random literal
            int index = Random.Range(0, Axiom.Length);
            char axe = Axiom[index];
            char c = FlipLiteral(axe);           
            if (c != '\0')
            {
                Axiom = Axiom.ReplaceAt(index, c);               
            }
        }
    }

    private char FlipLiteral(char axe)
    {
        char c = '\0';
        if (char.IsUpper(axe))
        {
            c = LSystem.RandomBody();
        }
        else if (char.IsLower(axe))
        {
            c = LSystem.RandomConfiguration();
        }
        else if (char.IsDigit(axe))
        {
            c = LSystem.RandomJoint();
        }
        return c;
    }

    public string MutateRule(string rule)
    {
        float flipProb = 0.2f;
        if (rule.Contains("["))
        {
            // Gets the outmost bracket pair
            int bracketStart = rule.IndexOf('[') + 1;
            int bracketLength = rule.LastIndexOf(']') - bracketStart;

            string sub = rule.Substring(bracketStart, bracketLength);
            if (sub.Equals(""))
            {
                // Empty bracket - add something!
                string add = LSystem.AddJointRule() + LSystem.AddBodyRule();
                rule = rule.ReplaceAt(bracketStart, bracketLength, add);
            }
            else
            {
                // Flip random literals
                for (int i = 0; i < sub.Length; i++)
                {
                    float rand = Random.Range(0, 1.0f);
                    if (rand < flipProb)
                    {
                        char c = FlipLiteral(sub[i]);
                        if (c != '\0')
                        {
                            rule.ReplaceAt(bracketStart + i, c);
                        }
                    }
                }
            }
        }
        else
        {
            float rand = Random.Range(0, 1.0f);
            do
            {
                int index = Random.Range(0, rule.Length);
                char axe = rule[index];
                char c = FlipLiteral(axe);
                if (c != '\0')
                {
                    rule = rule.ReplaceAt(index, c);
                }
                rand = Random.Range(0, 1.0f);
            } while (rand < 0.3f);
        }
        return rule;
    }

    public void Mutate()
    {
        // Initially, we only mutate the axiom or inside brackets of production rules
        float rand = Random.Range(0, 1.0f);
        float axeMut = 0.1f;
        float ruleMut = 0.3f;
        float ruleAdd = 0.3f;
        float ruleReplace = 0.5f;

        LSystem lsystem = new LSystem();
        string orig = lsystem.CreateFromGenoType(this.ProductionRules, this.Axiom, Depth);
        string mutated = orig;
        while (mutated.Equals(orig))
        {
            // Assumes no brackets in axioms
            if (rand < axeMut)
            {
                MutateAxiom(rand);
            }
            else
            {
                List<char> keys = new List<char>(ProductionRules.Keys);
                bool didMutate = false;
                while (!didMutate)
                {
                    foreach (char key in keys)
                    {
                        rand = Random.Range(0, 1.0f);
                        if (rand < ruleReplace)
                        {
                            ProductionRules[key] = lsystem.CreateRule(key);
                            didMutate = true;
                        }
                        else
                        {
                            rand = Random.Range(0, 1.0f);
                            if (rand < ruleMut)
                            {
                                string rule = ProductionRules[key];
                                ProductionRules[key] = MutateRule(ProductionRules[key]);
                                didMutate = didMutate || !rule.Equals(ProductionRules[key]);
                            }
                        }
                    }
                }
            }
            rand = Random.Range(0, 1.0f);
            if (rand < ruleAdd)
            {
                char c = lsystem.all[Random.Range(0, lsystem.all.Length)];
                while (ProductionRules.ContainsKey(c))
                {
                    c = lsystem.all[Random.Range(0, lsystem.all.Length)];
                }
                string rule = lsystem.CreateRule(c);
                ProductionRules.Add(c, rule);
            }

            orig = lsystem.CreateFromGenoType(this.ProductionRules, this.Axiom, Depth);
        }
    }
}
